export default {
    install(Vue, config) {

        /**
         * 是否为空
         * @param {*} value 值
         */
        function isEmpty(value) {
            return value == null || value === '';
        }

        /**
         * 错误消息对象
         */
        function Errors() {
            Object.defineProperty(this, 'length', { value: 0, writable: true });
        }

        /**
         * 扩展错误消息原型
         */
        Object.defineProperties(Errors.prototype, {
            splice: { value: Array.prototype.splice },
            [Symbol.iterator]: { value: Array.prototype[Symbol.iterator] },
            append: { value(k, v) { this[k] = this[this.length++] = v } },
            clear: { value() { this.length = 0; for (let i in this) { delete this[i] } } }
        });

        /*----------------------------------------------------------------------------------------------------*/

        /**
         * 验证
         * @param {Object} data 对象数据
         * @param {Object} config 验证配置
         */
        Vue.prototype.$validate = function (data = {}, config = {}) {
            return new Promise((resolve, reject) => {
                let count = 0, errors = new Errors();
                Object.entries(config).map(([key, value]) => [
                    key, data[key],
                    (value instanceof Array ? value : [value]).map(rule => {
                        switch (typeof rule) {
                            case 'string':
                                return { required: true, message: rule };
                            case 'function':
                                return { validate: rule };
                            default:
                                return rule;
                        }
                    })
                ]).forEach(([key, value, rules]) => {
                    key in errors || rules.forEach(rule => {
                        Object.entries(rule).filter(([k]) => {
                            return k in this.$validate.rules && ++count;
                        }).forEach(([k, r]) => {
                            this.$validate.rules[k](value, r, (p, m = rule.message) => setTimeout(() => {
                                if (!p) errors.append(key, m);
                                if (!--count) errors.length ? reject(errors) : resolve(data);
                            }));
                        });
                    });
                });
            });
        }

        /**
         * 验证规则
         */
        Vue.prototype.$validate.rules = Object.assign({
            validate(value, param, next) {
                param instanceof Function && param(value, next);
            },
            required(value, param, next) {
                next(!param || !isEmpty(value));
            },
            type(value, param, next) {
                if (isEmpty(value)) {
                    next(true);
                } else {
                    switch (param) {
                        case Boolean:
                            next(typeof value === 'boolean'); break;
                        case Number:
                            next(typeof value === 'number'); break;
                        case String:
                            next(typeof value === 'string'); break;
                        case Date:
                            next(value instanceof Date); break;
                        case RegExp:
                            next(value instanceof RegExp); break;
                        case Function:
                            next(value instanceof Function); break;
                        case Array:
                            next(value instanceof Array); break;
                        case Object:
                            next(value instanceof Object); break;
                        case 'int':
                            next(value == parseInt(value)); break;
                        case 'number':
                            next(!isNaN(value.toString() || NaN)); break;
                        case 'date':
                            next(!isNaN(new Date(value.toString()).valueOf())); break;
                        default:
                            next(true); break;
                    }
                }
            },
            not(value, param, next) {
                next(isEmpty(value) || value !== param);
            },
            eq(value, param, next) {
                next(isEmpty(value) || value === param);
            },
            lt(value, param, next) {
                next(isEmpty(value) || value < param);
            },
            gt(value, param, next) {
                next(isEmpty(value) || value > param);
            },
            min(value, param, next) {
                next(isEmpty(value) || value >= param);
            },
            max(value, param, next) {
                next(isEmpty(value) || value <= param);
            },
            length(value, param, next) {
                next(isEmpty(value) || value.length != null && value.length == param);
            },
            minLength(value, param, next) {
                next(isEmpty(value) || value.length != null && value.length >= param);
            },
            maxLength(value, param, next) {
                next(isEmpty(value) || value.length != null && value.length <= param);
            },
            regexp(value, param, next) {
                next(isEmpty(value) || param instanceof RegExp && param.test(value));
            },
            includes(value, param, next) {
                next(isEmpty(value) || value.includes != null && value.includes(param));
            },
            enum(value, param, next) {
                next(isEmpty(value) || param.includes != null && param.includes(value));
            },
            each(value, param, next) {
                next(isEmpty(value) || [...value].reduce((p, o, i, a) => p && param(o, i, a), true));
            }
        }, config);

    }
}